/**
 * lexer
 *
 *
 */

/***************************
 ** Section 1: Definitions
 ***************************/
%{

#include "../sql/Stat.h"
#include "bison_parser.h"
#include <stdio.h>
#include <sstream>


#define TOKEN(name) { return SQL_##name; }

static std::stringstream strbuf;

int ment_char = 0; 

int ment_lex(YYSTYPE* yylval_param, YYLTYPE* yylloc_param, yyscan_t yyscanner);


char g_typoBuf[512] = {0};   // 存最近一次 typo 提示

static const char* g_key_hint[] = {
    "SELECT","FROM","WHERE","INSERT","UPDATE","DELETE","CREATE","DROP","TABLE","INDEX","VIEW","JOIN","ON","AS"
};
static const int g_key_hint_cnt = sizeof(g_key_hint)/sizeof(g_key_hint[0]);

static const char* g_keywords[] = {
    "ADD", "ALL", "ALTER", "AND", "AS", "ASC", "BETWEEN", "BY", "CALL", "CASE",
    "CHAR", "COLUMN", "CREATE", "CROSS", "CSV", "DATE", "DEFAULT", "DELETE",
    "DESC", "DISTINCT", "DOUBLE", "DROP", "ELSE", "END", "EXCEPT", "EXISTS",
    "EXPLAIN", "EXTRACT", "FALSE", "FILE", "FLOAT", "FOR", "FROM", "FULL",
    "GROUP", "HAVING", "HISTORY", "IF", "IMPORT", "INDEX", "INNER", "INSERT",
    "INTEGER", "INTERSECT", "INTO", "IS", "JOIN", "KEY", "LEFT", "LIKE", "LIMIT",
    "LOAD", "LOCAL", "LONG", "MERGE", "MINUS", "NATURAL", "NOT", "NULL",
    "NVARCHAR", "OFFSET", "ON", "OR", "ORDER", "OUTER", "PARAMETER", "PRIMARY",
    "PRIMARY_KEY", "RENAME", "REPLACE", "RESTRICT", "RIGHT", "SCHEMA", "SELECT",
    "SET", "SHOW", "SORTED", "SPATIAL", "TABLE", "TEMPORARY", "TEXT", "THEN",
    "TIME", "TIMESTAMP", "TO", "TOP", "TRUE", "TRUNCATE", "UNION", "UNIQUE",
    "UPDATE", "USE", "USING", "VALUES", "VARCHAR", "VIRTUAL", "VIEW", "WHEN",
    "WHERE", "WITH"
};
static const int g_keyword_cnt = sizeof(g_keywords) / sizeof(g_keywords[0]);

/* 2. 二分查找 */
static int cmp_key(const void* p1, const void* p2) {
    return _stricmp(*(const char**)p1, *(const char**)p2);
}

//单字符编辑距离
int ed1(const char* a, const char* b) {
    int la = (int)strlen(a), lb = (int)strlen(b);
    if (abs(la - lb) > 1) return 999;
    int diff = 0;
    for (int i = 0, j = 0; i < la || j < lb; ++i, ++j) {
        if (a[i] != b[j]) {
            ++diff;
            if (la > lb) --j;
            else if (la < lb) --i;
        }
        if (diff > 1) return 999;
    }
    return diff;
}

static int is_keyword(const char* word) {
    return bsearch(&word, g_keywords, g_keyword_cnt,
                   sizeof(g_keywords[0]), cmp_key) != NULL;
}

//关键字映射表
static const struct { const char *word; int tok; } g_kw[] = {
    {"select",SQL_SELECT}, {"from",SQL_FROM}, {"where",SQL_WHERE},
    {"insert",SQL_INSERT}, {"delete",SQL_DELETE}, {"create",SQL_CREATE},
    {"drop",SQL_DROP}, {"table",SQL_TABLE}, {"index",SQL_INDEX},
    {"view",SQL_VIEW}, {"join",SQL_JOIN}, {"on",SQL_ON}, {"as",SQL_AS},
    {"into",SQL_INTO}, {"values",SQL_VALUES}, {"set",SQL_SET},
    {"group",SQL_GROUP}, {"order",SQL_ORDER}, {"by",SQL_BY},
    {"having",SQL_HAVING}, {"limit",SQL_LIMIT}, {"offset",SQL_OFFSET},
    {"union",SQL_UNION}, {"intersect",SQL_INTERSECT}, {"except",SQL_EXCEPT},
    {"and",SQL_AND}, {"or",SQL_OR}, {"not",SQL_NOT},
    {"in",SQL_IN}, {"exists",SQL_EXISTS}, {"between",SQL_BETWEEN},
    {"like",SQL_LIKE}, {"is",SQL_IS}, {"null",SQL_NULL},
    {"true",SQL_TRUE}, {"false",SQL_FALSE}, {"primary",SQL_PRIMARY},
    {"key",SQL_KEY}, {"unique",SQL_UNIQUE}, {"default",SQL_DEFAULT},
    {"varchar",SQL_VARCHAR}, {"int",SQL_INT}, {"float",SQL_FLOAT},
    {"double",SQL_DOUBLE}, {"text",SQL_TEXT}, {"bool",SQL_BOOL},
    {"timestamp",SQL_TIMESTAMP}, {"date",SQL_DATE}, {"time",SQL_TIME}
};
#define KW_N (sizeof(g_kw)/sizeof(g_kw[0]))

//大小写不敏感查表
static int kw_lookup(const char *s){
    for(int i=0;i<KW_N;++i)
        if(_stricmp(s,g_kw[i].word)==0) return g_kw[i].tok;
    return 0;   // 0 代表不是关键字
}

#define YY_USER_ACTION \
    yylloc->first_line = yylloc->last_line; \
    yylloc->first_column = yylloc->last_column; \
    for (int i = 0; yytext[i] != '\0'; i++) { \
        if (yytext[i] == '\n') { \
            yylloc->last_line++; \
            yylloc->last_column = 1; \
        } else { \
            yylloc->last_column++; \
        } \
    }


#define PRINT_TOKEN(type, lexeme) \
    printf("[Token]:[%s, %s, %d, %d]\n", \
           type, lexeme, yylloc->first_line, yylloc->first_column)
%}
%x singlequotedstring

/***************************
 ** Section 2: Rules
 ***************************/
/* Define the output files */
%option header-file="flex_lexer.h"
%option outfile="flex_lexer.cpp"


/* Make reentrant */
%option reentrant
%option bison-bridge

/* performance tweeks */
%option never-interactive
%option batch

/* other flags */
%option noyywrap
%option nounput
%option warn
%option case-insensitive
%option prefix="ment_"
%option bison-locations

%option extra-type="ment::SQLParserResult*"


%s COMMENT

/***************************
 ** Section 3: Rules
 ***************************/
%%
"--"[^\n]*                  { 
    /* 匹配注释但不包括换行符 */
}

[ \t\n]+                    /* skip whitespace */

DEALLOCATE                  { ment_char = SQL_DEALLOCATE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(DEALLOCATE) }
PARAMETERS                  { ment_char = SQL_PARAMETERS; PRINT_TOKEN("KEYWORD", yytext); TOKEN(PARAMETERS) }
INTERSECT                   { ment_char = SQL_INTERSECT; PRINT_TOKEN("KEYWORD", yytext); TOKEN(INTERSECT) }
TEMPORARY                   { ment_char = SQL_TEMPORARY; PRINT_TOKEN("KEYWORD", yytext); TOKEN(TEMPORARY) }
TIMESTAMP                   { ment_char = SQL_TIMESTAMP; PRINT_TOKEN("KEYWORD", yytext); TOKEN(TIMESTAMP) }
DISTINCT                    { ment_char = SQL_DISTINCT; PRINT_TOKEN("KEYWORD", yytext); TOKEN(DISTINCT) }
NVARCHAR                    { ment_char = SQL_NVARCHAR; PRINT_TOKEN("KEYWORD", yytext); TOKEN(NVARCHAR) }
RESTRICT                    { ment_char = SQL_RESTRICT; PRINT_TOKEN("KEYWORD", yytext); TOKEN(RESTRICT) }
TRUNCATE                    { ment_char = SQL_TRUNCATE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(TRUNCATE) }
ANALYZE                     { ment_char = SQL_ANALYZE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(ANALYZE) }
BETWEEN                     { ment_char = SQL_BETWEEN; PRINT_TOKEN("KEYWORD", yytext); TOKEN(BETWEEN) }
CASCADE                     { ment_char = SQL_CASCADE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(CASCADE) }
COLUMNS                     { ment_char = SQL_COLUMNS; PRINT_TOKEN("KEYWORD", yytext); TOKEN(COLUMNS) }
CONTROL                     { ment_char = SQL_CONTROL; PRINT_TOKEN("KEYWORD", yytext); TOKEN(CONTROL) }
DEFAULT                     { ment_char = SQL_DEFAULT; PRINT_TOKEN("KEYWORD", yytext); TOKEN(DEFAULT) }
EXECUTE                     { ment_char = SQL_EXECUTE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(EXECUTE) }
EXPLAIN                     { ment_char = SQL_EXPLAIN; PRINT_TOKEN("KEYWORD", yytext); TOKEN(EXPLAIN) }
HISTORY                     { ment_char = SQL_HISTORY; PRINT_TOKEN("KEYWORD", yytext); TOKEN(HISTORY) }
INTEGER                     { ment_char = SQL_INTEGER; PRINT_TOKEN("KEYWORD", yytext); TOKEN(INTEGER) }
NATURAL                     { ment_char = SQL_NATURAL; PRINT_TOKEN("KEYWORD", yytext); TOKEN(NATURAL) }
PREPARE                     { ment_char = SQL_PREPARE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(PREPARE) }
PRIMARY_KEY                 { ment_char = SQL_PRIMARY_KEY; PRINT_TOKEN("KEYWORD", yytext); TOKEN(PRIMARY_KEY) }
PRIMARY                     { ment_char = SQL_PRIMARY; PRINT_TOKEN("KEYWORD", yytext); TOKEN(PRIMARY) }
SCHEMAS                     { ment_char = SQL_SCHEMAS; PRINT_TOKEN("KEYWORD", yytext); TOKEN(SCHEMAS) }
SPATIAL                     { ment_char = SQL_SPATIAL; PRINT_TOKEN("KEYWORD", yytext); TOKEN(SPATIAL) }
VARCHAR                     { ment_char = SQL_VARCHAR; PRINT_TOKEN("KEYWORD", yytext); TOKEN(VARCHAR) }
VIRTUAL                     { ment_char = SQL_VIRTUAL; PRINT_TOKEN("KEYWORD", yytext); TOKEN(VIRTUAL) }
BEFORE                      { ment_char = SQL_BEFORE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(BEFORE) }
COLUMN                      { ment_char = SQL_COLUMN; PRINT_TOKEN("KEYWORD", yytext); TOKEN(COLUMN) }
CREATE                      { ment_char = SQL_CREATE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(CREATE) }
DELETE                      { ment_char = SQL_DELETE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(DELETE) }
DIRECT                      { ment_char = SQL_DIRECT; PRINT_TOKEN("KEYWORD", yytext); TOKEN(DIRECT) }
DOUBLE                      { ment_char = SQL_DOUBLE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(DOUBLE) }
ESCAPE                      { ment_char = SQL_ESCAPE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(ESCAPE) }
EXCEPT                      { ment_char = SQL_EXCEPT; PRINT_TOKEN("KEYWORD", yytext); TOKEN(EXCEPT) }
EXISTS                      { ment_char = SQL_EXISTS; PRINT_TOKEN("KEYWORD", yytext); TOKEN(EXISTS) }
EXTRACT                     { ment_char = SQL_EXTRACT; PRINT_TOKEN("KEYWORD", yytext); TOKEN(EXTRACT) }
GLOBAL                      { ment_char = SQL_GLOBAL; PRINT_TOKEN("KEYWORD", yytext); TOKEN(GLOBAL) }
HAVING                      { ment_char = SQL_HAVING; PRINT_TOKEN("KEYWORD", yytext); TOKEN(HAVING) }
IMPORT                      { ment_char = SQL_IMPORT; PRINT_TOKEN("KEYWORD", yytext); TOKEN(IMPORT) }
INSERT                      { ment_char = SQL_INSERT; PRINT_TOKEN("KEYWORD", yytext); TOKEN(INSERT) }
ISNULL                      { ment_char = SQL_ISNULL; PRINT_TOKEN("KEYWORD", yytext); TOKEN(ISNULL) }
OFFSET                      { ment_char = SQL_OFFSET; PRINT_TOKEN("KEYWORD", yytext); TOKEN(OFFSET) }
RENAME                      { ment_char = SQL_RENAME; PRINT_TOKEN("KEYWORD", yytext); TOKEN(RENAME) }
SCHEMA                      { ment_char = SQL_SCHEMA; PRINT_TOKEN("KEYWORD", yytext); TOKEN(SCHEMA) }
SELECT                      { ment_char = SQL_SELECT; PRINT_TOKEN("KEYWORD", yytext); TOKEN(SELECT) }
SORTED                      { ment_char = SQL_SORTED; PRINT_TOKEN("KEYWORD", yytext); TOKEN(SORTED) }
TABLES                      { ment_char = SQL_TABLES; PRINT_TOKEN("KEYWORD", yytext); TOKEN(TABLES) }
UNIQUE                      { ment_char = SQL_UNIQUE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(UNIQUE) }
UNLOAD                      { ment_char = SQL_UNLOAD; PRINT_TOKEN("KEYWORD", yytext); TOKEN(UNLOAD) }
UPDATE                      { ment_char = SQL_UPDATE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(UPDATE) }
VALUES                      { ment_char = SQL_VALUES; PRINT_TOKEN("KEYWORD", yytext); TOKEN(VALUES) }
AFTER                       { ment_char = SQL_AFTER; PRINT_TOKEN("KEYWORD", yytext); TOKEN(AFTER) }
ALTER                       { ment_char = SQL_ALTER; PRINT_TOKEN("KEYWORD", yytext); TOKEN(ALTER) }
ARRAY                       { ment_char = SQL_ARRAY; PRINT_TOKEN("KEYWORD", yytext); TOKEN(ARRAY) }
CROSS                       { ment_char = SQL_CROSS; PRINT_TOKEN("KEYWORD", yytext); TOKEN(CROSS) }
DELTA                       { ment_char = SQL_DELTA; PRINT_TOKEN("KEYWORD", yytext); TOKEN(DELTA) }
FLOAT                       { ment_char = SQL_FLOAT; PRINT_TOKEN("KEYWORD", yytext); TOKEN(FLOAT) }
GROUP                       { ment_char = SQL_GROUP; PRINT_TOKEN("KEYWORD", yytext); TOKEN(GROUP) }
INDEX                       { ment_char = SQL_INDEX; PRINT_TOKEN("KEYWORD", yytext); TOKEN(INDEX) }
INNER                       { ment_char = SQL_INNER; PRINT_TOKEN("KEYWORD", yytext); TOKEN(INNER) }
LIMIT                       { ment_char = SQL_LIMIT; PRINT_TOKEN("KEYWORD", yytext); TOKEN(LIMIT) }
LOCAL                       { ment_char = SQL_LOCAL; PRINT_TOKEN("KEYWORD", yytext); TOKEN(LOCAL) }
USE                         { ment_char = SQL_USE;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(USE) }
MERGE                       { ment_char = SQL_MERGE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(MERGE) }
MINUS                       { ment_char = SQL_MINUS; PRINT_TOKEN("KEYWORD", yytext); TOKEN(MINUS) }
ORDER                       { ment_char = SQL_ORDER; PRINT_TOKEN("KEYWORD", yytext); TOKEN(ORDER) }
OUTER                       { ment_char = SQL_OUTER; PRINT_TOKEN("KEYWORD", yytext); TOKEN(OUTER) }
RIGHT                       { ment_char = SQL_RIGHT; PRINT_TOKEN("KEYWORD", yytext); TOKEN(RIGHT) }
TABLE                       { ment_char = SQL_TABLE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(TABLE) }
UNION                       { ment_char = SQL_UNION; PRINT_TOKEN("KEYWORD", yytext); TOKEN(UNION) }
USING                       { ment_char = SQL_USING; PRINT_TOKEN("KEYWORD", yytext); TOKEN(USING) }
WHERE                       { ment_char = SQL_WHERE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(WHERE) }
CALL                        { ment_char = SQL_CALL;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(CALL) }
CASE                        { ment_char = SQL_CASE;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(CASE) }
CHAR                        { ment_char = SQL_CHAR;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(CHAR) }
DATE                        { ment_char = SQL_DATE;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(DATE) }
DESC                        { ment_char = SQL_DESC;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(DESC) }
DROP                        { ment_char = SQL_DROP;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(DROP) }
ELSE                        { ment_char = SQL_ELSE;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(ELSE) }
FILE                        { ment_char = SQL_FILE;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(FILE) }
FROM                        { ment_char = SQL_FROM;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(FROM) }
FULL                        { ment_char = SQL_FULL;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(FULL) }
HASH                        { ment_char = SQL_HASH;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(HASH) }
HINT                        { ment_char = SQL_HINT;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(HINT) }
INTO                        { ment_char = SQL_INTO;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(INTO) }
JOIN                        { ment_char = SQL_JOIN;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(JOIN) }
LEFT                        { ment_char = SQL_LEFT;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(LEFT) }
LIKE                        { ment_char = SQL_LIKE;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(LIKE) }
ILIKE                       { ment_char = SQL_ILIKE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(ILIKE) }
LOAD                        { ment_char = SQL_LOAD;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(LOAD) }
LONG                        { ment_char = SQL_LONG;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(LONG) }
NULL                        { ment_char = SQL_NULL;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(NULL) }
PLAN                        { ment_char = SQL_PLAN;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(PLAN) }
SHOW                        { ment_char = SQL_SHOW;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(SHOW) }
TEXT                        { ment_char = SQL_TEXT;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(TEXT) }
THEN                        { ment_char = SQL_THEN;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(THEN) }
TIME                        { ment_char = SQL_TIME;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(TIME) }
VIEW                        { ment_char = SQL_VIEW;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(VIEW) }
WHEN                        { ment_char = SQL_WHEN;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(WHEN) }
WITH                        { ment_char = SQL_WITH;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(WITH) }
ADD                         { ment_char = SQL_ADD;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(ADD) }
ALL                         { ment_char = SQL_ALL;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(ALL) }
AND                         { ment_char = SQL_AND;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(AND) }
ASC                         { ment_char = SQL_ASC;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(ASC) }
CSV                         { ment_char = SQL_CSV;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(CSV) }
END                         { ment_char = SQL_END;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(END) }
FOR                         { ment_char = SQL_FOR;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(FOR) }
INT                         { ment_char = SQL_INT;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(INT) }
KEY                         { ment_char = SQL_KEY;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(KEY) }
NOT                         { ment_char = SQL_NOT;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(NOT) }
OFF                         { ment_char = SQL_OFF;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(OFF) }
SET                         { ment_char = SQL_SET;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(SET) }
TBL                         { ment_char = SQL_TBL;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(TBL) }
TOP                         { ment_char = SQL_TOP;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(TOP) }
AS                          { ment_char = SQL_AS;    PRINT_TOKEN("KEYWORD", yytext); TOKEN(AS) }
BY                          { ment_char = SQL_BY;    PRINT_TOKEN("KEYWORD", yytext); TOKEN(BY) }
IF                          { ment_char = SQL_IF;    PRINT_TOKEN("KEYWORD", yytext); TOKEN(IF) }
IN                          { ment_char = SQL_IN;    PRINT_TOKEN("KEYWORD", yytext); TOKEN(IN) }
IS                          { ment_char = SQL_IS;    PRINT_TOKEN("KEYWORD", yytext); TOKEN(IS) }
OF                          { ment_char = SQL_OF;    PRINT_TOKEN("KEYWORD", yytext); TOKEN(OF) }
ON                          { ment_char = SQL_ON;    PRINT_TOKEN("KEYWORD", yytext); TOKEN(ON) }
OR                          { ment_char = SQL_OR;    PRINT_TOKEN("KEYWORD", yytext); TOKEN(OR) }
TO                          { ment_char = SQL_TO;    PRINT_TOKEN("KEYWORD", yytext); TOKEN(TO) }
SECOND                      { ment_char = SQL_SECOND; PRINT_TOKEN("KEYWORD", yytext); TOKEN(SECOND) }
MINUTE                      { ment_char = SQL_MINUTE; PRINT_TOKEN("KEYWORD", yytext); TOKEN(MINUTE) }
HOUR                        { ment_char = SQL_HOUR;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(HOUR) }
DAY                         { ment_char = SQL_DAY;    PRINT_TOKEN("KEYWORD", yytext); TOKEN(DAY) }
MONTH                       { ment_char = SQL_MONTH;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(MONTH) }
YEAR                        { ment_char = SQL_YEAR;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(YEAR) }
TRUE                        { ment_char = SQL_TRUE;   PRINT_TOKEN("KEYWORD", yytext); TOKEN(TRUE) }
FALSE                       { ment_char = SQL_FALSE;  PRINT_TOKEN("KEYWORD", yytext); TOKEN(FALSE) }
BOOL                        { ment_char = SQL_BOOL;   TOKEN(BOOL) }
BOOLEAN                     { ment_char = SQL_BOOL;   TOKEN(BOOL) }


"=="                        { ment_char = SQL_EQUALS;    PRINT_TOKEN("OPERATOR", yytext); TOKEN(EQUALS) }
"!="                        { ment_char = SQL_NOTEQUALS; PRINT_TOKEN("OPERATOR", yytext); TOKEN(NOTEQUALS) }
"<>"                        { ment_char = SQL_NOTEQUALS; PRINT_TOKEN("OPERATOR", yytext); TOKEN(NOTEQUALS) }
"<="                        { ment_char = SQL_LESSEQ;    PRINT_TOKEN("OPERATOR", yytext); TOKEN(LESSEQ) }
">="                        { ment_char = SQL_GREATEREQ; PRINT_TOKEN("OPERATOR", yytext); TOKEN(GREATEREQ) }
"||"                        { ment_char = SQL_CONCAT;    PRINT_TOKEN("OPERATOR", yytext); TOKEN(CONCAT) }

"+"                         { ment_char = '+';  PRINT_TOKEN("OPERATOR", yytext); return '+'; }
"-"                         { ment_char = '-';  PRINT_TOKEN("OPERATOR", yytext); return '-'; }
"*"                         { ment_char = '*';  PRINT_TOKEN("OPERATOR", yytext); return '*'; }
"/"                         { ment_char = '/';  PRINT_TOKEN("OPERATOR", yytext); return '/'; }
"("                         { ment_char = '(';  PRINT_TOKEN("OPERATOR", yytext); return '('; }
")"                         { ment_char = ')';  PRINT_TOKEN("OPERATOR", yytext); return ')'; }
"{"                         { ment_char = '{';  PRINT_TOKEN("OPERATOR", yytext); return '{'; }
"}"                         { ment_char = '}';  PRINT_TOKEN("OPERATOR", yytext); return '}'; }
","                         { ment_char = ',';  PRINT_TOKEN("OPERATOR", yytext); return ','; }
"."                         { ment_char = '.';  PRINT_TOKEN("OPERATOR", yytext); return '.'; }
";"                         { ment_char = ';';  PRINT_TOKEN("OPERATOR", yytext); return ';'; }
"<"                         { ment_char = '<';  PRINT_TOKEN("OPERATOR", yytext); return '<'; }
">"                         { ment_char = '>';  PRINT_TOKEN("OPERATOR", yytext); return '>'; }
"="                         { ment_char = '=';  PRINT_TOKEN("OPERATOR", yytext); return '='; }
"^"                         { ment_char = '^';  PRINT_TOKEN("OPERATOR", yytext); return '^'; }
"%"                         { ment_char = '%';  PRINT_TOKEN("OPERATOR", yytext); return '%'; }
":"                         { ment_char = ':';  PRINT_TOKEN("OPERATOR", yytext); return ':'; }
"?"                         { ment_char = '?';  PRINT_TOKEN("OPERATOR", yytext); return '?'; }
"["                         { ment_char = '[';  PRINT_TOKEN("OPERATOR", yytext); return '['; }
"]"                         { ment_char = ']';  PRINT_TOKEN("OPERATOR", yytext); return ']'; }
"|"                         { ment_char = '|';  PRINT_TOKEN("OPERATOR", yytext); return '|'; }


-?[0-9]+\.[0-9]*            {
                                ment_char = SQL_FLOATVAL;
                                yylval->fval = atof(yytext);
                                PRINT_TOKEN("CONST_FLOAT", yytext);
                                return SQL_FLOATVAL;
                            }
\.[0-9]+                    {
                                ment_char = SQL_FLOATVAL;
                                yylval->fval = atof(yytext);
                                PRINT_TOKEN("CONST_FLOAT", yytext);
                                return SQL_FLOATVAL;
                            }


-?[0-9]+                    {
                                ment_char = SQL_INTVAL;
                                yylval->ival = atol(yytext);
                                PRINT_TOKEN("CONST_INT", yytext);
                                return SQL_INTVAL;
                            }


\"[^\"]*\"                  {
                                ment_char = SQL_IDENTIFIER;
                                char* temp = _strdup(yytext + 1);
                                temp[strlen(temp) - 1] = '\0';
                                yylval->sval = temp;
                                PRINT_TOKEN("CONST_STRING", yylval->sval);
                                return SQL_IDENTIFIER;
                            }


\'                          {
                                ment_char = SQL_SQL_ILLEGAL;
                                BEGIN(singlequotedstring);
                                strbuf.str("");
                                strbuf.clear();
                                PRINT_TOKEN("SINGLE_QUOTE_START", "'");
                            }
<singlequotedstring>\'\'    {
                                ment_char = SQL_SQL_ILLEGAL;
                                strbuf << '\'';
                                PRINT_TOKEN("SINGLE_QUOTE_INSIDE", "''");
                            }
<singlequotedstring>[^']+   {
                                ment_char = SQL_SQL_ILLEGAL;
                                strbuf << yytext;
                                PRINT_TOKEN("SINGLE_QUOTE_CONTENT", yytext);
                            }
<singlequotedstring>\'      {
                                ment_char = SQL_STRING;
                                BEGIN(INITIAL);
                                yylval->sval = _strdup(strbuf.str().c_str());
                                PRINT_TOKEN("SINGLE_QUOTE_END", "'");
                                return SQL_STRING;
                            }
<singlequotedstring><<EOF>> {
                                char buf[256];
                                snprintf(buf, sizeof(buf),
                                        "[SQL-Lexer-Error] Unterminated string literal | "
                                        "Line: %d, Col: %d | "
                                        "Hint: Add a closing single quote ' at the end of the string.",
                                        yylloc->first_line, yylloc->first_column);
                                fprintf(stderr, "%s\n", buf);
                                return 0;
                            }


[A-Za-z_][A-Za-z0-9_]*      {
                                int t = kw_lookup(yytext);
                                if (t) {                      // 是关键字
                                    ment_char = t;
                                    PRINT_TOKEN("KEYWORD", yytext);
                                    return t;
                                }
                                /* 不是关键字，检查是否像关键字 typo */
                                int best = -1, bestd = 999;
                                for (int i = 0; i < g_key_hint_cnt; ++i) {
                                    int d = ed1(yytext, g_key_hint[i]);
                                    if (d < bestd) { bestd = d; best = i; }
                                }
                                if (bestd == 1) {             // 编辑距离 ==1
                                    snprintf(g_typoBuf, sizeof(g_typoBuf),
                                            "[SQL-Lexer-ERROR] \"%s\" looks like a typo, did you mean \"%s\"? (line %d, col %d)",
                                            yytext, g_key_hint[best], yylloc->first_line, yylloc->first_column);
                                }

                                /* ---------------------------------------- */
                                
                                ment_char = SQL_IDENTIFIER;
                                Stat* e = Stat::makeColumnRef(_strdup(yytext));
                                e->first_line   = yylloc->first_line;
                                e->first_column = yylloc->first_column;
                                yylval->stat    = e;
                                PRINT_TOKEN("IDENTIFIER", yytext);
                                return SQL_IDENTIFIER;
                            }




.                           {
                                ment_char = SQL_SQL_ILLEGAL;
                                fprintf(stderr, "[LEXER ERROR] Illegal character '%c' at line %d, column %d\n",
                                        yytext[0],
                                        yylloc->first_line,
                                        yylloc->first_column);
                                return 0;
                            }

%%
/***************************
 ** Section 4: User code
 ***************************/

int yyerror(YYLTYPE* llocp, ment::SQLParserResult* result, yyscan_t scanner, const char *msg) {
    fprintf(stderr, "[SQL-Lexer-Error] %s at line %d, column %d\n",
            msg, llocp->first_line, llocp->first_column);
    return 0;
}